/* Copyright 2001,2002,2003 NAH6 BV
 * All Rights Reserved
 *
 *  $Header: /var/lib/cvs/secphone/ui/database/SysContacts.cpp,v 1.7 2003/11/21 12:39:24 itsme Exp $
 *
 *
 * this file handles the system contacts database
 *
 * this module is used to lookup names for a phonenumber,
 * or to find the preferred number for an contacts entry.
 *
 */
#include "SysContacts.h"
#include <windows.h>
#include <TCHAR.h>
#include "debug.h"

// contacts db can be search by
//    - lastname, firstname
//    - firstname
//    - company

const DWORD phoneFields[]= { 
    DBSYSCONTACT_HOME_PAGE, DBSYSCONTACT_TEL_MOBILE, DBSYSCONTACT_TEL_CAR,
    DBSYSCONTACT_TEL_HOME, DBSYSCONTACT_TEL_WORK,
    DBSYSCONTACT_TEL_WORK2, DBSYSCONTACT_TEL_HOME2,
    DBSYSCONTACT_TEL_PAGER, DBSYSCONTACT_TEL_RADIO,
    DBSYSCONTACT_TEL_WORK_FAX, DBSYSCONTACT_TEL_HOME_FAX, 
    DBSYSCONTACT_TEL_ASSISTANT, 
};



WCHAR *SysContacts::DatabaseName()
{
    return L"Contacts Database";
}

SysContacts::SysContacts()
{
}

SysContacts::~SysContacts()
{
}

void SysContacts::Open()
{
    m_db.Open(DatabaseName());
}

//----------------------------------------------------------
// this function depends on the current state of the phone system in the world.
// in the early 90s things changed quite a lot, this may happen again...
//
// attempt to determine the length of the countrycode of a phone number.
//
int CountryCodeLength(const TCHAR *number)
{
    switch(number[0])
    {
        case '1': return 1;
        case '2': 
            if (number[1]=='0' || number[1]=='7')
                return 2;
            else
                return 3;
        case '3':
            if (number[1]=='5' || number[1]=='7' || number[1]=='8')
                return 3;
            else
                return 2;
        case '4':
            if (number[1]=='2')
                return 3;
            else
                return 2;
        case '5':
            if (number[1]=='0' || number[1]=='9')
                return 3;
            else
                return 2;
        case '6':
            if (number[1]=='7' || number[1]=='8' || number[1]=='9')
                return 3;
            else
                return 2;
        case '7': return 1;
        case '8':
            if (number[1]=='0' || number[1]=='3' || number[1]=='5' || number[1]=='7' || number[1]=='8' || number[1]=='9')
                return 3;
            else
                return 2;
        case '9':
            if (number[1]=='6' || number[1]=='7' || number[1]=='9')
                return 3;
            else
                return 2;
    }
    return 0;
}
// leave only dialable items in the number.
void SanitizeNumber(const TCHAR *number, TCHAR *sanitized, int max)
{
    int j=0;
    for (DWORD i=0 ; i<_tcslen(number) && j<max-1 ; i++)
    {
        TCHAR c= tolower(number[i]);
        //todo: for now I leave out the possibility of having 'abcd' dtmf in phone numbers
        if (iswdigit(c) || c=='*' || c=='#' || (c=='+' && j==0)/* || (c>='a' && c<='d') */)
            sanitized[j++]= c;
    }
    sanitized[j]= 0;
}

// this function is passed to 'LinearSearch' to search the database for a matching
// entry.
// returning '1' or '-1'  does not actually mean this is an ordered compare.
int PhoneNumberMatch(const CEPROPVAL *pv, DWORD param)
{
    if ((pv->propid&0xffff)!=CEVT_LPWSTR)
        return 1;
    if (pv->val.lpwstr==NULL)
        return 1;

    TCHAR dbnumber[64];
    SanitizeNumber(pv->val.lpwstr, dbnumber, 64);

    TCHAR *number= (TCHAR*)param;

    //debug("matching %ls (%ls) to %ls\n", dbnumber, pv->val.lpwstr, number);
    // minimum number length is 3.   ( f.i. voicemail = 121 )
    if (_tcslen(dbnumber)<=2)
    {
        //debug("   - rejected because db nr too short\n");
        return -1;
    }

    // if both start with '+' both must be complete numbers
    if (dbnumber[0]=='+' && number[0]=='+')
    {
        //debug("   both full numbers\n");
        return _tcscmp(dbnumber, number);
    }

    TCHAR *dbfirst= (dbnumber[0]=='+')
        ? dbnumber+CountryCodeLength(dbnumber+1) 
        : dbnumber;

    TCHAR *nrfirst= (number[0]=='+')
        ? number+CountryCodeLength(number+1) 
        : number;

    // now both numbers are stripped of their country code,
    //  or start with a country or carrier dependend international accesscode
    //  or start with a local longdistance access code.
    if (_tcscmp(dbfirst, nrfirst)==0)
    {
        //debug("   - equal\n");
        return 0;
    }

    // now start stripping digits from nr, and see if we get a substring match
    while (_tcslen(nrfirst)>=3)
    {
        TCHAR *p= _tcsstr(dbnumber, nrfirst);
        // substring match must match until the end of the number.
        if (p!=NULL && _tcslen(p)==_tcslen(nrfirst))
        {
            //debug("   - match of substring '%ls' in '%ls'\n", nrfirst, dbnumber);
            return 0;
        }

        nrfirst++;
    }
    //debug("   - no match found\n");
    return 1;
}


//----------------------------------------------------------
TCHAR *GetNumberType(DWORD dwFieldId)
{
    switch(dwFieldId)
    {
    case DBSYSCONTACT_HOME_PAGE:    return L"sp";
    case DBSYSCONTACT_TEL_MOBILE:   return L"m";
    case DBSYSCONTACT_TEL_HOME:     return L"h";
    case DBSYSCONTACT_TEL_WORK:     return L"w";
    case DBSYSCONTACT_TEL_CAR:      return L"car";
    case DBSYSCONTACT_TEL_WORK_FAX: return L"wf";
    case DBSYSCONTACT_TEL_HOME_FAX: return L"hf";
    case DBSYSCONTACT_TEL_HOME2:    return L"h2";
    case DBSYSCONTACT_TEL_ASSISTANT:return L"ast";
    case DBSYSCONTACT_TEL_WORK2:    return L"w2";
    case DBSYSCONTACT_TEL_PAGER:    return L"pgr";
    case DBSYSCONTACT_TEL_RADIO:    return L"rdo";
    }
    return NULL;
}


bool SysContacts::FindEntryByNumber(const TCHAR *wnumber, SysContactsEntry &entry)
{
    TCHAR dbnr[64];
    SanitizeNumber(wnumber, dbnr, 64);

    DWORD dwFieldId;
    CEOID oid;

    if (!m_db.LinearSearch(sizeof(phoneFields)/sizeof(DWORD), phoneFields, &dwFieldId, &oid, (DWORD)wnumber, &PhoneNumberMatch))
        return false;

    CeRecord *rec= m_db.ReadRecord(oid);

    _tcsncpy(m_nrbuf, rec->GetString(DBSYSCONTACT_NAME_FULL), 63);
    m_nrbuf[63]= 0;

    entry.name= m_nrbuf;
    entry.number= wnumber;
    entry.numbertype= GetNumberType(dwFieldId);

    delete rec;

    return true;
}

void SysContacts::AddEntry(const SysContactsEntry &entry)
{
    CeRecord rec(2);
    rec.AddString(DBSYSCONTACT_TEL_MOBILE, entry.number);
    rec.AddString(DBSYSCONTACT_NAME_FULL, entry.name);
    m_db.CommitRecord(rec);

    debug("added record for '%ls'  nr '%ls'  oid=%08lx\n",
        entry.name, entry.number, rec.m_oid);
}
#define CRYPTOPHONE_PREFIX L"secure:"

bool isCryptoPhoneEntry(const WCHAR *entry)
{
    return entry!=NULL && wcsncmp(entry, CRYPTOPHONE_PREFIX, sizeof(CRYPTOPHONE_PREFIX)/sizeof(WCHAR))==0;
}

bool SysContacts::AddDataCallEntry(const WCHAR *wnumber)
{
    DWORD dwFieldId;
    CEOID oid;

    if (!m_db.LinearSearch(sizeof(phoneFields)/sizeof(DWORD), phoneFields, &dwFieldId, &oid, (DWORD)wnumber, &PhoneNumberMatch))
    {
        debug("number '%ls' not found in contacts\n", wnumber);
        return false;
    }
    CeRecord *rec= m_db.ReadRecord(oid);

    if (rec==NULL)
    {
        debug("ERROR: number '%ls' found, but oid %08lx disappeared\n", wnumber, oid);
        return false;
    }
    WCHAR buf[64]; _snwprintf(buf, 64, CRYPTOPHONE_PREFIX L"%ls", wnumber);

    WCHAR *homepage= rec->GetString(DBSYSCONTACT_HOME_PAGE);
    if (!isCryptoPhoneEntry(homepage))
    {
        rec->SetString(DBSYSCONTACT_HOME_PAGE, buf);

        debug("adding new sp entry to oid %08lx\n", oid);
        return m_db.CommitRecord(*rec);
    }
    else {
        debug("number '%ls' already has a cryptophone entry: '%ls'\n", wnumber, homepage);
        return true;
    }

    // todo: delete 'rec' - now it is leaked.
}

bool SysContacts::FindPreferredNumber(CEOID oid, WCHAR *wnumber, int maxnumlen)
{
   CeRecord *rec= m_db.ReadRecord(oid);

   if (rec==NULL)
       return false;

   for (int i=0 ; i<sizeof(phoneFields)/sizeof(DWORD) ; i++)
   {
       const WCHAR *str= rec->GetString(phoneFields[i]);
       if (str!=NULL)
       {
           SanitizeNumber(str, wnumber, maxnumlen);
           if (wcslen(wnumber)>1)
               return true;
       }
   }
   return false;
}
